/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package fr.inria.openmath.omapi.implementation;

/**
 *
 * @author Mr.Sam
 */

import java.util.Vector;
import fr.inria.openmath.omapi.*;

/**
 * This Class is a basic implementation for the fr.inria.openmath.omapi.Node interface.
 *
 */
public class NodeImpl implements Node {

    short type;

    NodeImpl parent, previousSibling, nextSibling;

    int childNb;

    NodeImpl lastChild;

    String comments;                   // all comments concatenated

    Vector pis;              // strings representing the processing instructions

    AttributeList attributeList;

    private class Attribute {

        Symbol name;

        Node value;

        Attribute next;

        public Attribute (Symbol name, Node value) {
            this.name = name;
            this.value = value;
        }

        public Attribute cloneAttribute() {
            return new Attribute(name, value);
        }

    }

    private class AttributeList {

        int attributesNb;

        Attribute firstAttribute;

        int cachedAttributeIndex;

        Attribute cachedAttribute;

        public AttributeList cloneAttributeList() {
            AttributeList cloneList = new AttributeList();
            Attribute srcAttribute, cloneAttribute;

            cloneList.attributesNb = this.attributesNb;
            if (this.firstAttribute != null) {
                srcAttribute = this.firstAttribute;
                cloneList.firstAttribute = srcAttribute.cloneAttribute();
                cloneAttribute = cloneList.firstAttribute;
                while (srcAttribute.next != null) {
                    srcAttribute = srcAttribute.next;
                    cloneAttribute.next = srcAttribute.cloneAttribute();
                    cloneAttribute = cloneAttribute.next;
                }
            }
            return cloneList;
        }

    }

    public NodeImpl () {}

    public NodeImpl (short type) {
        this.type = type;
    }

    /**
     * Return the string associated to a node type.
     * @params Type to translate.
     * @return The string translation of internal node type.
     *
     */
    public String typeToString(short type) {
        switch (type) {
            case OM_INT32: 
                return "int";
            case OM_BIGINT: 
                return "bigint";
            case OM_FLOAT64: 
                return "float64";
            case OM_BYTEARRAY: 
                return "bytearray";
            case OM_VAR: 
                return "var";
            case OM_STRING: 
                return "string";
            case OM_SYMBOL: 
                return "symbol";
            case OM_COMMENT: 
                return "comment";
            case OM_APP: 
                return "app";
            case OM_BIND: 
                return "bind";
            case OM_ERROR: 
                return "error";
            case OM_OBJECT: 
                return "object";
            default: 
                return "uknown";
        }
    }

    /**
     * @see java.lang.Object#toString
     */
    public String toString() {
        return typeToString(type);
    }

    private boolean isParent(Node node) {
        if (node != null)
            return (this == node.getParent()) ? true : false;
        else
            return false;
    }

    /**
     * @see fr.inria.openmath.omapi.Node#getNodeName
     */
    public String getNodeName() {
        return typeToString(type);
    }

    /**
     * @see fr.inria.openmath.omapi.Node#getNodeType
     */
    public short getNodeType() {
        return type;
    }

    /**
     * @see fr.inria.openmath.omapi.Node#getParent
     */
    public Node getParent() {
        return parent;
    }

    /**
     * @see fr.inria.openmath.omapi.Node#getChildNodes
     */
    public NodeList getChildNodes() {
        if (childNb > 0) {
            return new NodeListImpl(this);
        } else {
            return null;
        }
    }

    /**
     * @see fr.inria.openmath.omapi.Node#hasChildNodes
     */
    public boolean hasChildNodes() {
        return (childNb > 0) ? true : false;
    }

    /**
     * @see fr.inria.openmath.omapi.Node#getFirstChild
     */
    public Node getFirstChild() {
        NodeImpl n;
        n = (NodeImpl) (((NodeImpl) this).lastChild);
        if (n == null)
            return null;
        while (n.previousSibling != null) {
            n = n.previousSibling;
        }
        return n;
    }

    /**
     * @see fr.inria.openmath.omapi.Node#getLastChild
     */
    public Node getLastChild() {
        return this.lastChild;
    }

    /**
     * @see fr.inria.openmath.omapi.Node#getPreviousSibling
     */
    public Node getPreviousSibling() {
        return previousSibling;
    }

    /**
     * @see fr.inria.openmath.omapi.Node#getNextSibling
     */
    public Node getNextSibling() {
        return nextSibling;
    }

    /**
     * @see fr.inria.openmath.omapi.Node#appendChild
     */
    public Node appendChild(Node newChild) throws OMException {
        Node last;

        if (newChild == null)
            return null;
        if (this.type != OM_OBJECT && this.type != OM_APP
                && this.type != OM_BIND && this.type != OM_ERROR) {
            throw new OMException("can't add child to this type of node!",
                    OMException.OME_HIERARCHY_REQUEST);
        }
        if (this.isParent(newChild)) {
            removeChild(newChild);
        }
        last = this.getLastChild();
        if (last != null) {
            // non empty list
            ((NodeImpl) last).nextSibling = (NodeImpl) newChild;
        }
        ((NodeImpl) newChild).previousSibling = (NodeImpl) last;
        ((NodeImpl) newChild).nextSibling = null;
        ((NodeImpl) newChild).parent = (NodeImpl) this;
        this.lastChild = (NodeImpl) newChild;
        childNb++;
        return newChild;
    }

    /**
     * @see fr.inria.openmath.omapi.Node#insertBefore
     */
    public Node insertBefore(Node newChild, Node refChild) throws OMException {
        if (newChild == null)
            return null;

        if (refChild == null) {
            // insert as last child
            appendChild(newChild);
        } else {
            // normal case: insert before refchild
            if (!this.isParent(refChild)) {
                throw new OMException(
                        "reference child is not a child of this node! can't insert before."
                        , OMException.OME_NODE_NOT_FOUND);
            }
            if (this.isParent(newChild)) {
                removeChild(newChild);
            }

            ((NodeImpl) newChild).nextSibling = (NodeImpl) refChild;
            ((NodeImpl) newChild).previousSibling =
                    (NodeImpl) ((NodeImpl) refChild).previousSibling;
            if (((NodeImpl) newChild).previousSibling != null) {
                ((NodeImpl) newChild).previousSibling.nextSibling =
                        (NodeImpl) newChild;
            }
            ((NodeImpl) refChild).previousSibling = (NodeImpl) newChild;
        }
        ((NodeImpl) newChild).parent = (NodeImpl) this;
        childNb++;
        return newChild;
    }

    /**
     * @see fr.inria.openmath.omapi.Node#replaceChild
     */
    public Node replaceChild(Node newChild, Node oldChild) throws OMException {
        if (newChild == null)
            return null;

        if (this.isParent(oldChild)) {
            if (this.isParent(newChild)) {
                removeChild(newChild);
            }
            ((NodeImpl) newChild).previousSibling =
                    ((NodeImpl) oldChild).previousSibling;
            ((NodeImpl) newChild).nextSibling =
                    ((NodeImpl) oldChild).nextSibling;
            ((NodeImpl) oldChild).previousSibling =
                    ((NodeImpl) oldChild).nextSibling =
                    ((NodeImpl) oldChild).parent = null;

            ((NodeImpl) newChild).parent = (NodeImpl) this;
            return oldChild;
        } else {
            throw new OMException(
                    "old child is not a child of this node! can't replace it.",
                    OMException.OME_NODE_NOT_FOUND);
        }
    }

    /**
     * @see fr.inria.openmath.omapi.Node#removeChild
     */
    public Node removeChild(Node oldChild) throws OMException {
        if (oldChild == null)
            return null;

        if (this.isParent(oldChild)) {
            if (lastChild == oldChild) {
                lastChild = ((NodeImpl) oldChild).previousSibling;
            } else if (((NodeImpl) oldChild).nextSibling != null) {
                ((NodeImpl) oldChild).nextSibling.previousSibling =
                        ((NodeImpl) oldChild).previousSibling;
            }

            if (((NodeImpl) oldChild).previousSibling != null) {
                ((NodeImpl) oldChild).previousSibling.nextSibling =
                        ((NodeImpl) oldChild).nextSibling;
            }
        } else {
            throw new OMException(
                    "old child is not a child of this node! can't remove it.",
                    OMException.OME_NODE_NOT_FOUND);
        }
        ((NodeImpl) oldChild).parent = null;
        ((NodeImpl) oldChild).previousSibling = null;
        ((NodeImpl) oldChild).nextSibling = null;
        childNb--;
        return oldChild;
    }

    /**
     * @see fr.inria.openmath.omapi.Node#cloneNode
     */
    public Node cloneNode(boolean deep) {
        NodeImpl mainCloneNode, cloneNode, srcNode;

        mainCloneNode = new NodeImpl();
        mainCloneNode.type = this.type;
        mainCloneNode.comments = this.comments;
        mainCloneNode.attributeList = this.attributeList.cloneAttributeList();
        if (deep) {
            srcNode = (NodeImpl) this.getFirstChild();
            while (srcNode != null) {
                cloneNode = (NodeImpl) srcNode.cloneNode(true);
                mainCloneNode.appendChild(cloneNode);
                srcNode = srcNode.nextSibling;
            }
        }
        return mainCloneNode;
    }

    /**
     * @see fr.inria.openmath.omapi.Node#getAttributeNumber
     */
    public int getAttributeNumber() {
        if (attributeList == null) {
            return 0;
        } else {
            return attributeList.attributesNb;
        }
    }

    private Attribute getNthAttribute(int index) {
        Attribute a;
        int skip;

        if (attributeList == null)
            return null;
        if ((attributeList.cachedAttribute != null) &&
                (attributeList.cachedAttributeIndex <= index)) {
            skip = index - attributeList.cachedAttributeIndex;
            a = attributeList.cachedAttribute;
        } else {
            skip = index;
            a = attributeList.firstAttribute;
        }
        while (skip > 0) {
            if (a == null)
                break;
            skip--;
            a = a.next;
        }
        attributeList.cachedAttributeIndex = index - skip;
        attributeList.cachedAttribute = a;
        return a;
    }

    /**
     * @see fr.inria.openmath.omapi.Node#getNthAttributeValue
     */
    public Node getNthAttributeValue(int index) {
        return (getNthAttribute(index)).value;
    }

    /**
     * @see fr.inria.openmath.omapi.Node#getNthAttributeName
     */
    public Symbol getNthAttributeName(int index) {
        return (getNthAttribute(index)).name;
    }

    /**
     * @see fr.inria.openmath.omapi.Node#getAttribute
     */
    public Node getAttribute(Symbol name) {
        Attribute a;
        if (attributeList != null) {
            a = attributeList.firstAttribute;
            while (a != null) {
                if (a.name.equals(name)) {
                    return a.value;
                }
                a = a.next;
            }
        }
        return null;
    }


    /**
     * @see fr.inria.openmath.omapi.Node#setAttribute
     */
    public void setAttribute(Symbol name, Node value) {
        Attribute a;
        if (attributeList == null) {
            attributeList = new AttributeList();
        }
        a = new Attribute(name, value);
        a.next = attributeList.firstAttribute;
        attributeList.firstAttribute = a;
        attributeList.attributesNb++;
        if (attributeList.cachedAttribute != null)
            attributeList.cachedAttributeIndex++;
    }

    /**
     * @see fr.inria.openmath.omapi.Node#removeAttribute
     */
    public Node removeAttribute(Symbol name) {
        Attribute a, removed;
        if (attributeList != null) {
            a = attributeList.firstAttribute;
            if (a.name.equals(name)) {
                attributeList.firstAttribute = a.next;
                attributeList.attributesNb--;
                a.next = null;
                return a.value;
            }
            while (a.next != null) {
                if (a.next.name.equals(name)) {
                    removed = a.next;
                    a.next = a.next.next;
                    removed.next = null;
                    attributeList.attributesNb--;
                    return removed.value;
                }
                a = a.next;
            }
        }
        return null;
    }

    /**
     * @see fr.inria.openmath.omapi.Node#getComments
     */
    public String getComments() {
        return comments;
    }

    /**
     * @see fr.inria.openmath.omapi.Node#setComments
     */
    public String setComments(String comments) {
        String old;
        old = this.comments;
        this.comments = comments;
        return old;
    }

    /**
     * @see fr.inria.openmath.omapi.Node#getPInstructions
     */
    public Vector getPInstructions() {
        return pis;
    }

    /**
     * @see fr.inria.openmath.omapi.Node#setPInstructions
     */
    public Vector setPInstructions(Vector pis) {
        Vector old;

        old = this.pis;
        this.pis = pis;
        return old;
    }

}
